import json
import os.path

from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator
from django.db import models

# Create your models here.
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
from modelops_backend.settings import BASE_DIR
from functools import partial

LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())



def validate_filename(value):
    print("文件名是:",value)
    # 定义允许的字符集合，这里假设只允许字母、数字、下划线和点号
    allowed_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.')
    # 检查文件名中的每个字符是否都在允许的字符集合中
    if not all(char in allowed_chars for char in value):
        raise ValidationError('文件名包含非法字符,文件名是{}'.format(value))

def constrcut_file_save_path(instance, filename, mode):
    # file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
    # dirs = os.path.join(BASE_DIR, 'static', instance.task, instance.area, instance.name)
    dirs = os.path.join('static', instance.task, instance.area, instance.name)
    if not os.path.exists(dirs):
        os.makedirs(dirs)
    path = os.path.join(dirs, filename)
    if mode == 'train':
        instance.train_data_path = path
    elif mode == 'dev':
        instance.dev_data_path = path
    elif mode == 'test':
        instance.test_data_path = path
    else:
        instance.process_code_path = path
    return path


class Dataset(models.Model):
    name = models.CharField(db_index=True, max_length=100, unique=True, help_text="数据集名称,不可重复")
    task = models.CharField(db_index=True, max_length=100, default="命名实体识别",
                            choices=(("命名实体识别", "命名实体识别"), ("关系抽取", "关系抽取"),
                                     ("实体关系联合抽取", "实体关系联合抽取")),
                            help_text="数据集支持的任务,默认为命名实体识别")
    area = models.CharField(db_index=True, max_length=100, default="医疗", help_text="数据集所属领域,默认为医疗")
    short_description = models.TextField(help_text="数据集的简单描述")
    long_description = models.TextField(help_text="数据集的详细描述,为markdown格式的纯文本")
    sample = models.TextField(help_text="数据集的样例展示", default="")
    data_source = models.TextField(max_length=100, help_text="数据集的存储位置", default="")
    created = models.DateTimeField(auto_now_add=True, help_text="数据集创建时间")
    owner = models.ForeignKey('auth.User', related_name='datasets', on_delete=models.SET_DEFAULT, default=1)
    # TODO:设置外键为统计次数
    experiment_times = models.IntegerField(default=0, help_text="数据集被实验使用的次数", )

    train_data = models.FileField(verbose_name="训练数据集", upload_to=partial(constrcut_file_save_path, mode="train"),
                                  default="", validators=[
            FileExtensionValidator(allowed_extensions=['txt','json']),
            # validate_filename  # 使用自定义的验证器检查文件名
        ])
    train_data_path = models.CharField(max_length=200, help_text="自动填入的训练文件存储路径", default="")
    dev_data = models.FileField(verbose_name="验证数据集", upload_to=partial(constrcut_file_save_path, mode="dev"),
                                default="",validators=[
            FileExtensionValidator(allowed_extensions=['txt','json']),
            # validate_filename  # 使用自定义的验证器检查文件名
        ] )
    dev_data_path = models.CharField(max_length=200, help_text="自动填入的验证文件存储路径", default="")
    test_data = models.FileField(verbose_name="测试数据集", upload_to=partial(constrcut_file_save_path, mode="test"),
                                 default="",validators=[
            FileExtensionValidator(allowed_extensions=['txt','json']),
            # validate_filename  # 使用自定义的验证器检查文件名
        ] )
    test_data_path = models.CharField(max_length=200, help_text="自动填入的测试文件存储路径", default="")
    process_code = models.FileField(verbose_name="数据预处理代码,文件名为interface.py",
                                    upload_to=partial(constrcut_file_save_path, mode="file"), default="", validators=[
            FileExtensionValidator(allowed_extensions=['py']),
            # validate_filename  # 使用自定义的验证器检查文件名
        ])
    process_code_path = models.CharField(max_length=200, help_text="自动填入的数据预处理代码存储路径", default="")
    download_url = models.CharField(max_length=200, help_text="数据集的下载地址", default="")

    def save(self, *args, **kwargs):
        if not self.sample and self.train_data:
            # 假设train_data是一个JSON文件
            self.sample = self._extract_sample_from_train_data()
        super().save(*args, **kwargs)  # 调用父类的save方法来保存所有的字段

    def _extract_sample_from_train_data(self):
        try:
            if self.train_data.name.endswith('.json'):
                # 打开文件读取样本
                with self.train_data.open('r') as file:
                    sample = json.load(file)[0]
                # 将样本转换为JSON字符串
                return json.dumps(sample)
            elif self.train_data.name.endswith('.txt'):
                with self.train_data.open('r') as f:
                    words = []  # 用以存储当前遍历到的样本
                    labels = []  # 用以存储当前遍历到的样本的标签
                    tmp_line = None  # 用以记录当前遍历到的样本及其标签
                    # 读取数据集的每一行
                    for line in f:
                        # 如果当前行是空行或'\n'，则来到了两个样本的分界点
                        if line == "" or line == "\n":
                            # 如果words不空，则说明当前遍历到的样本及其标签已存储到了words和labels中，则将words和labels的内容记录在tmp_line中
                            if words:
                                tmp_line = {"words": words, "labels": labels}
                                words = []
                                labels = []
                        # 否则说明当前样本还未遍历完毕
                        else:
                            # 将当前行进行分割得到当前的token和label
                            splits = line.split()
                            words.append(splits[0])
                            labels.append(splits[-1])
                        # 如果tmp_line不为空，则说明当前样本及其标签已存储到了tmp_line中，则将tmp_line的内容记录在features中
                        if tmp_line:
                            # 按照labels_format的格式将tmp_line中的标签转换为对应的数据类型
                            text = " ".join(tmp_line['words'])
                            label_list = tmp_line['labels']
                            # 构造样本特征
                            line = {"text": text, "labels": label_list}
                            return json.dumps(line)
        except Exception as e:
            # 处理可能的错误，如文件打不开，文件为空等
            return str(e)


    class Meta:
        ordering = ('task', 'area', 'created')
        verbose_name = '数据集'
        verbose_name_plural = '数据集'


class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField(help_text="代码片段")
    linenos = models.BooleanField(default=False)
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)

    owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
    highlighted = models.TextField()

    class Meta:
        ordering = ('created',)

    def save(self, *args, **kwargs):
        """
           使用`pygments`库创建一个高亮显示的HTML表示代码段。
           """
        lexer = get_lexer_by_name(self.language)
        linenos = self.linenos and 'table' or False
        options = self.title and {'title': self.title} or {}
        formatter = HtmlFormatter(style=self.style, linenos=linenos,
                                  full=True, **options)
        self.highlighted = highlight(self.code, lexer, formatter)
        super(Snippet, self).save(*args, **kwargs)


class ModelRepo(models.Model):
    """
    深度学习模型存储,ModelOps后端仅存储基本信息
    核心代码需要开发人员用git管理和push pull
    会自动建立git仓库 并返回git地址
    """
    model_name = models.CharField(db_index=True, max_length=100, help_text="完整的模型名称,不可重复")
    short_description = models.TextField(help_text="模型的简单描述")
    long_description = models.TextField(help_text="模型的详细描述,为markdown格式的纯文本")
    created = models.DateTimeField(auto_now_add=True, help_text="模型创建时间")
    owner = models.ForeignKey('auth.User', related_name='models', on_delete=models.SET_DEFAULT, default=1)
    task = models.CharField(db_index=True, max_length=100,
                            choices=(("命名实体识别", "命名实体识别"), ("关系抽取", "关系抽取"),
                                     ("实体关系联合抽取", "实体关系联合抽取")), default="命名实体识别",
                            help_text="模型支持的任务,默认为命名实体识别")

    class Meta:
        ordering = ('task', 'created')
        verbose_name = '模型'
        verbose_name_plural = '模型'


class Experiment(models.Model):
    """记录实验的基本信息"""
    dataset = models.ForeignKey(Dataset, related_name='experiments', on_delete=models.DO_NOTHING,
                                help_text="实验使用的数据集", db_index=True)
    model = models.ForeignKey(ModelRepo, related_name='experiments', on_delete=models.DO_NOTHING,
                              help_text="实验使用的模型", db_index=True)
    dataset_name = models.CharField(max_length=100, help_text="数据集名称", default="")
    model_name = models.CharField(max_length=100, help_text="模型名称", default="")

    created = models.DateTimeField(auto_now_add=True, help_text="实验创建时间")
    owner = models.ForeignKey('auth.User', related_name='experiments', on_delete=models.SET_DEFAULT, default=1)
    task = models.CharField(db_index=True, max_length=100, default="命名实体识别",
                            choices=(("命名实体识别", "命名实体识别"), ("关系抽取", "关系抽取"),
                                     ("实体关系联合抽取", "实体关系联合抽取")))
    run_status = models.CharField(db_index=True, max_length=100, choices=(
        ("running", "running"), ("finished", "finished"), ("failed", "failed"), ("deployed", "deployed")),
                                  default="running")
    model_config = models.TextField(help_text="模型训练时的配置信息,为json格式的字符串")
    metric = models.TextField(max_length=500, help_text="模型的评价指标结果,为json格式的字符串", default="")
    train_log = models.TextField(help_text="模型训练时的日志信息,为json格式的字符串,用于可视化", default="")
    model_version = models.IntegerField(default=1, help_text="模型版本号,同一个模型,每次训练后自动生成一个版本")
    model_path = models.CharField(max_length=200, help_text="训练完成的模型的存储路径,自动填入", default="")
    branch = models.CharField(max_length=100, help_text="git仓库的分支,默认为master,可以进行选择", default="master")

    description = models.TextField(help_text="实验的简单描述,可不填", default="")

    class Meta:
        ordering = ('task','dataset_name', 'model_name', 'created')
        verbose_name = '实验'
        verbose_name_plural = '实验'
